package com.AdvancedDemos.单元测试_反射_注解.注解;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

public class Learn注解 {

        /*
        注解（Annotation）
        Java代码里的特殊标记，例如:@Override、@Test等，作用是：让其他程序根据注解信息来决定怎么处理接下来的代码
         */
    //注意：注解可以用在类上、构造函数上、方法上、成员变量上、参数上 等位置
    /**
     自定义注解的格式：
     public @interface 注解名称 {
     public 属性类型 属性名（）default 默认值；
     }

     其中 属性类型 可以是任意类型，包括枚举类型

     ◉ 特殊属性名：value
     如果注解中只有一个value属性，使用注解时， value="属性值" 中 value= 可以不写！！
     如果同时还有其他属性，那么则不可以省略value= ；而如果同时其他属性又都有默认值，那么可以让其他属性使用默认值，那么此时将只剩value属性需要赋值，于是又可以省略value=了
     */
    //注解的原理：
    //public @interface MyTest1{
    //  String aaa();
    //  boolean bbb();
    //  String[] ccc();
    //}
    //
    //   编译↓
    //  .class
    //   反编译↓
    //  .java
    //
    //public interface MyTest1 extends Annotation{
    //  public abstract String aaa();
    //  public abstract boolean bbb();
    //  public abstract String[] ccc();
    //}
    //
    //可以看到，注解本质上就是一个接口继承自Annotation接口，当中的属性都是抽象方法
    //
    //而在用@注解(...)使用注解时
    //@MyTest1(aaa="李四",bbb=true,ccc={"Go","Python"})
    //
    //本质上是一个注解接口的实现类对象，实现了该注解接口以及Annotation接口，该实现类对象会封装属性的值（"李四" true {"Go","Python"}），之后可以通过对象中的重写的成员方法（aaa bbb ccc）来获取属性值
        /*
        元注解：
        修饰注解的注解

        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.METHOD})
        public @interface Test{
            ...
        }

        元注解一共就2个 ：  @Target  @Retention（retention /rɪˈtenʃn/ n.保留，保持）

        @Target
        作用：声明被修饰的注解只能在哪些位置使用
        @Target(ElementType.TYPE)
        1、TYPE 类，接口
        2、FIELD 域(成员变量)
        3、METHOD 成员方法
        4、PARAMETER 方法参数
        5、CONSTRUCTOR 构造器(构造函数)
        6、LOCAL_VARIABLE  局部变量

        @Retention
        作用：声明注解的保留周期
        @Retention(RetentionPolicy.RUNTIME)
        1、SOURCE
        只作用在源码阶段，字节码文件中不存在
        2、CLASS（默认值）
        保留到字节码文件阶段，运行阶段不存在
        3、RUNTIME（开发常用）
        一直保留到运行阶段
         */

    /**
     * 加注解后如何识别注解呢？
     *
     * 解析注解：
     * ◉就是判断类上、方法上、成员变量上是否存在注解，并把注解里面的内容给解析出来
     * 如何解析注解？
     * ◉要解析谁上面的注解，就应该先拿到谁
     * ◉比如要解析类上面的注解，则应该先获取该类的Class对象，再通过Class对象解析其上面的注解
     * ◉比如要解析成员方法上面的注解，则应该获取到该成员方法的Method对象，再通过Method对象解析其上面的注解
     * ◉Class、Method、Field、Constructor都实现了AnnotatedElement接口，它们都拥有解析注解的能力
     AnnotatedElement接口提供了解析注解的方法             说明
     public Annotation[] getDeclaredAnnotation            获取当前对象上面的注解
     public T getDeclaredAnnotation(Class<T>  annotationClass)  获取指定的注解对象
     public boolean isAnnotationPresent(Class<Annotation> annotationClass) 判断当前对象上面是否存在某个注解
     */
    /*
     * 解析注解的案例
     *
     * ①定义注解MyAnnotation4，要求如下：
     *      包含属性：String Value()
     *      包含属性：double aaa()，默认值为100
     *      包含属性：String[] bbb()
     *      限制注解使用的位置：类和成员方法上
     *      指定注解的有效范围：一直到运行时
     * ②定义一个类叫:UseAnnotation4，在类中定义一个useMyAnnotation4方法，并在该类和其方法上使用MyAnnotation4注解
     * ③定义TestResolvingMyAnnotation4测试类，解析Demo类中的全部注解
     */

    /**
     * 注解应用场景：模拟Junit框架
     * <p>
     * 需求：定义若干个方法，只要加了MyTest注解，就会触发该方法执行。
     * <p>
     * 分析：
     * ①定义一个自定义注解MyTest，限制其只能加在方法上，存活范围是一直都在。
     * ②定义若干个方法，部分方法加上@MyTest注解修饰，部分方法不加。
     * ③模拟一个Junit程序，可以触发加了@MyTest注解的方法执行。
     */

    //如下：


    /**
     * ②定义若干个方法，部分方法加上@MyTest注解修饰，部分方法不加。
     */
//    @MyTest
    public void test1() {
        System.err.println("===test1===");
    }

    @MyTest
    public void test2() {
        System.err.println("===test2===");
    }

    //    @MyTest
    public void test3() {
        System.err.println("===test3===");
    }

    @MyTest
    public void test4() {
        System.err.println("===test4===");
    }


    /**
     * ③模拟一个Junit程序，可以触发加了@MyTest注解的方法执行。
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        //1、先得到Class对象
        Class c = Learn注解.class;
        //2、用Class对象得到类中全部方法
        Method[] methods = c.getDeclaredMethods();
        //3、解析函数上的注解---遍历方法判断函数上是否有某个注解
        for (int i = 0; i < methods.length; i++) {
            if (methods[i].isAnnotationPresent(MyTest.class)) {
                //说明当前方法上存在@MyTest注解，触发当前方法执行
                methods[i].invoke(new Learn注解());
                //===test2===
                //===test4===
            }
        }
    }
}

/**
 * ①定义一个自定义注解MyTest，限制其只能加在方法上，存活范围是一直都在。
 */
@Target(ElementType.METHOD)//表示该自定义注解只能加在方法上
@Retention(RetentionPolicy.RUNTIME)//表示该自定义注解一直存在到运行阶段
@interface MyTest {

}

